﻿
using Boilen.Primitives.Members;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;


namespace Boilen.Primitives.Implementers {

    /// <inheritdoc/>
    /// <summary>
    /// Implements a dependency property metadata override.
    /// </summary>
    public sealed class MetadataOverride<T> : ValueAccessor<T> {

        private readonly string ownerTypeName_;
        private readonly bool isInherited_;


        /// <inheritdoc/>
        public override string FieldName { get { return this.AccessorName + MemberKind; } }

        /// <inheritdoc/>
        public override string FieldTypeName { get { return this.ownerTypeName_; } }

        /// <inheritdoc/>
        public override string FieldModifiers { get { return ""; } }

        /// <summary>
        /// Gets or sets a value indicating whether the metadata should be overridden in silverlight as well as WPF.
        /// </summary>
        public bool OverrideInSilverlight { get; set; }


        /// <inheritdoc/>
        protected override IEnumerable<InitializationMember> Initializers {
            get {
                var wpfArgs = this.BuildRegistrationArguments( false );
                yield return new InitializationMember( this.AccessorName, w => this.WriteMetadataOverride( w, wpfArgs ) ) {
                    Condition = CompilationSymbol.NotSilverlight,
                    MemberScope = FieldTypeName,
                    WriteVariableAssignment = false
                };

                if( this.OverrideInSilverlight ) {
                    string defaultValue = GetDefaultValueString( true );
                    var silverlightInitializer = this.isInherited_
                        ? new InitializationMember( this.AccessorName, defaultValue )
                        : new InitializationMember( this.AccessorName, string.Format( "{0}.Set{1}(this, {2})", this.ownerTypeName_, this.AccessorName, defaultValue ) ) { WriteVariableAssignment = false };
                    silverlightInitializer.Condition = CompilationSymbol.Silverlight;
                    yield return silverlightInitializer;
                }
            }
        }

        /// <inheritdoc/>
        protected override AccessorMember Accessor {
            get { return null; }
        }

        /// <inheritdoc/>
        protected override bool EnsureDescription {
            get { return false; }
        }


        /// <inheritdoc/>
        public MetadataOverride( PartialType parent, DependencyProperty existingProperty, Type ownerType )
            : base( parent, existingProperty.Name, " " ) {
            Ensure.ArgTypeMatches<T>( existingProperty.PropertyType );

            this.ownerTypeName_ = this.Parent.TypeRepository.GetTypeName( ownerType );

            Type parentType = this.Parent.BaseType;
            this.isInherited_ = ownerType.IsAssignableFrom( parentType )
                || parentType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance )
                    .Any( p => p.Name == this.AccessorName && p.PropertyType == this.Type );

            this.OverrideInSilverlight = true;
        }

        /// <inheritdoc/>
        public MetadataOverride( PartialType parent, DependencyProperty existingProperty )
            : this( parent, existingProperty, existingProperty.OwnerType ) { }


        /// <summary>
        /// Assigns the values used for the <see cref="OverrideInSilverlight"/> property.
        /// </summary>
        [DefaultValue( "True" )]
        public MetadataOverride<T> SetOverrideInSilverlight( bool overrideInSilverlight ) {
            this.OverrideInSilverlight = overrideInSilverlight;
            return this;
        }


        /// <inheritdoc/>
        protected override IEnumerable<Member> GetMembers( ) {
            foreach( var initializer in this.Initializers )
                yield return initializer;
        }


        /// <summary>
        /// Creates arguments for registering a dependency property.
        /// </summary>
        private DependencyPropertyMetadata BuildRegistrationArguments( bool silverlight ) {
            var args = new DependencyPropertyMetadata( this.Parent.TypeRepository, silverlight ) { InlineSingleArgument = true };

            // Default value
            string defaultValue = this.GetDefaultValueString( silverlight );
            args.AddMetadataArgument( defaultValue, DependencyPropertyMetadataKind.Framework );

            return args;
        }


        private string GetDefaultValueString( bool silverlight ) {
            string assignedDefaultValue;
            if( !this.DefaultValues.TryGetValue( CompilationSymbol.None, out assignedDefaultValue ) ) {
                var key = silverlight ? CompilationSymbol.Silverlight : CompilationSymbol.NotSilverlight;
                this.DefaultValues.TryGetValue( key, out assignedDefaultValue );
            }

            string defaultValue;
            if( string.IsNullOrEmpty( assignedDefaultValue ) )
                defaultValue = "default(" + this.TypeName + ")";
            else if( assignedDefaultValue.StartsWith( "new " ) || assignedDefaultValue.StartsWith( this.TypeName ) || typeof( T ) == typeof( object ) )
                defaultValue = assignedDefaultValue;
            else
                defaultValue = "(" + this.TypeName + ")" + assignedDefaultValue;

            return defaultValue;
        }

        private void WriteMetadataOverride( ICodeWriter writer, DependencyPropertyMetadata args ) {
            writer.Write( "{0}.{1}.OverrideMetadata", this.ownerTypeName_, this.FieldName );
            using( Enclose.Parenthesis( writer ) ) {
                writer.Write( "typeof({0}), ", this.Parent.TypeName );
                args.Write( writer );
            }
        }
    }

}
